<?php

namespace App\Models\Trip\TripOrderRefund;

use App\Models\Orders\Repositories\Interfaces\OrdersInterface;
use App\Models\Plane\PlaneOrderPassengers\Repositories\Interfaces\PlaneOrderPassengerRepositoryInterface;
use App\Models\Plane\PlaneOrders\PlaneOrder;
use App\Models\Plane\PlaneOrders\Repositories\Interfaces\PlaneOrderRepositoryInterface;
use App\Models\Task\SnapTasks\SnapTask;
use App\Models\Train\TrainOrderPassengers\Repositories\Interfaces\TrainOrderPassengersRepositoryInterface;
use App\Models\Train\TrainOrderRefund\Refunds\dealAloneUpgradeTaskRefund;
use App\Models\Train\TrainOrderRefund\Refunds\dealNoTicketEditTaskRefund;
use App\Models\Train\TrainOrders\Repositories\Interfaces\TrainOrdersRepositoryInterface;
use App\Models\Train\TrainOrders\TrainOrder;
use App\Models\Trip\OrderBusiness\Repositories\Interfaces\BusinessOrderSnMapRepositoryInterface;
use App\Models\Trip\OrderPay\Repositories\Interfaces\OrderPayRepositoryInterface;
use App\Models\Trip\TripOrderCosts\Repositories\Interfaces\TripOrderCostsRepositoryInterface;
use App\Models\Trip\TripOrderCosts\TripOrderCost;
use App\Models\Trip\TripOrderRefund\Refunds\dealTicketPriceDiffRefund;
use App\Trip\Orders\TripType;
use App\Trip\Traits\ModelDateTrait;
use App\Trip\Wechat\Pay\RefundRes\RefundResult;
use App\Trip\Wechat\Pay\WechatPay;
use Carbon\Carbon;
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
use Eloquent;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Throwable;

/**
 * 退款业务 明细要拆分
 * 1 . 无票退款 按订单金额退款 明细 含订单金额 ,抢票费,选座费(整体不具象),保险费(整体) 每笔订单每个类型只有一个
 * 2 . 有票退款 按照乘客退款  明细 含乘客id 序号 , 票价 , 已匹配的选座费 ,车票差价, 保险费,改签退款 等
 * 3 . 已出票 未匹配的选座费 明细 含座位类型 未匹配数量 选座费
 *
 * @property int                                              $refund_id
 * @property string                                           $user_id
 *           用户id
 * @property string                                           $refund_sn
 *           退款单号
 * @property string                                           $order_sn
 *           订单单号
 * @property string                                           $title
 *           退款标题
 * @property string                                           $ticket_price    票价
 * @property string                                           $need_pay_sn
 *           退款金额小于0时，要支付的支付单号
 * @property string                                           $refund_amount
 *           退款金额
 * @property int                                              $refund_state
 *           退款状态 0 受理中 1 退款中 2 已到账- 3 退款失败(重试3次均失败)
 * @property string                                           $out_refund_id
 *           外部退票id
 * @property string                                           $failed_reason
 *           失败原因 ： 退票失败的原因
 * @property int                                              $has_ticket
 *           是否有票 1 有票 0 无票
 * @property string|null                                      $refund_at
 *           退款处理时间
 * @property string|null                                      $success_at
 *           退款到账时间
 * @property \Illuminate\Support\Carbon|null                  $created_at
 * @property \Illuminate\Support\Carbon|null                  $updated_at
 * @property string|null                                      $out_refund_no
 *           外部退款单号，主要是有可能一次退多笔
 * @property string                                           $origin_order_sn
 *           appid
 * @property string                                           $refund_desc
 *           退款描述 , 如退款需支付 加以描述
 * @property string                                           $out_order_id
 *           服务商关联的退款order id
 * @property int                                              $refund_type
 *           退款类型 1用户申请退款 2 无票退款 3 未匹配的坐席 4 改签原票需退款
 * @property string                                           $sub_order_sn
 *           子订单号, 如改签原票需退款,关联改签的订单便于查询以作他用
 * @property-read Collection|NoTicketRefundDetail[]           $no_ticket_details
 * @property-read int|null
 *                $no_ticket_details_count
 * @property-read TrainOrder|null                             $order
 * @property-read Collection|TripOrderPassengerRefundDetail[] $passenger_details
 * @property-read int|null
 *                $passenger_details_count
 * @property-read Collection|NoMatchSeatRefundDetail[]
 *                $unmatch_seat_details
 * @property-read int|null
 *                $unmatch_seat_details_count
 * @method static Builder|TripOrderRefund newModelQuery()
 * @method static Builder|TripOrderRefund newQuery()
 * @method static Builder|TripOrderRefund query()
 * @method static Builder|TripOrderRefund whereCreatedAt($value)
 * @method static Builder|TripOrderRefund whereFailedReason($value)
 * @method static Builder|TripOrderRefund whereHasTicket($value)
 * @method static Builder|TripOrderRefund whereNeedPaySn($value)
 * @method static Builder|TripOrderRefund whereOrderSn($value)
 * @method static Builder|TripOrderRefund whereOriginOrderSn($value)
 * @method static Builder|TripOrderRefund whereOutOrderId($value)
 * @method static Builder|TripOrderRefund whereOutRefundId($value)
 * @method static Builder|TripOrderRefund whereOutRefundNo($value)
 * @method static Builder|TripOrderRefund whereRefundAmount($value)
 * @method static Builder|TripOrderRefund whereRefundAt($value)
 * @method static Builder|TripOrderRefund whereRefundDesc($value)
 * @method static Builder|TripOrderRefund whereRefundId($value)
 * @method static Builder|TripOrderRefund whereRefundSn($value)
 * @method static Builder|TripOrderRefund whereRefundState($value)
 * @method static Builder|TripOrderRefund whereRefundType($value)
 * @method static Builder|TripOrderRefund whereSubOrderSn($value)
 * @method static Builder|TripOrderRefund whereSuccessAt($value)
 * @method static Builder|TripOrderRefund whereTicketPrice($value)
 * @method static Builder|TripOrderRefund whereTitle($value)
 * @method static Builder|TripOrderRefund whereUpdatedAt($value)
 * @method static Builder|TripOrderRefund whereUserId($value)
 * @property string                                           $out_trade_no 即支付单号 pay_sn 用于退款时传入
 * @property int                                              $refund_scope 退款作用域 2 的n次 或 暂未启用
 * @property string                                           $pay_amount 支付单支付的总金额 便于退款
 * @method static Builder|TripOrderRefund whereOutTradeNo($value)
 * @method static Builder|TripOrderRefund wherePayAmount($value)
 * @method static Builder|TripOrderRefund whereRefundScope($value)
 * @property int|null                                         $trip_type 出行类型 1 火车 2 飞机 3 酒店
 * @method static Builder|TripOrderRefund whereTripType($value)
 * @property-read PlaneOrder|null                             $plane_order
 * @property-read TrainOrder|null                             $train_order
 * @mixin Eloquent
 */
class TripOrderRefund extends Model
{
    use HasFactory, ModelDateTrait;

    const REFUND_FAILED_MAX_RETRY = 4;
    const REFUND_FAILED_BACKOFF   = [120, 600, 1800, 3600, 3600 * 24];

    // 涉及到退款可能要重试的情况 最好退的业务退款单号能固定

    // 退款失败重试次数 和下面 backoff 数量 对应
    const REFUND_STATE_WAIT_REFUNDING       = 0;
    const REFUND_STATE_REFUNDING            = 1;
    const REFUND_STATE_SUCCESS              = 2; // 发起退款待处理
    const REFUND_STATE_FAILED               = 3; // 退款中
    const REFUND_STATE_TICKET_RETURN_FAILED = 4; // 退款成功
    const REFUND_TYPE_USER_APPLY            = 1; // 退款失败
    const REFUND_TYPE_NO_TICKET             = 2; // 退票失败
    // 用户申请退款 需要流转订单状态
    const REFUND_TYPE_UNMATCHED_SEATS = 3;
    // 无票退款 需要流转订单状态
    const REFUND_TYPE_CHANGED_NEED_REFUND = 4;
    // 未匹配的坐席 不需要流转订单状态
    const REFUND_HAS_TICKET = 1;
    // 改签原票需退款 不需要流转订单状态
    const REFUND_NO_TICKET = 0;

    // 有票 无票标记
    const REFUND_SCOPE_ALL = 1;
    const REFUND_SCOPE_2   = 2;

    // 新增退款类型标记 1 全部 所有的费用 组合值表示 退款作用域 或的关系 如 1 代表全部 ,6 代表 2|4
    const REFUND_SCOPE_4   = 4;
    const REFUND_SCOPE_8   = 8;
    const REFUND_SCOPE_16  = 16;
    const REFUND_SCOPE_32  = 32;
    const REFUND_SCOPE_64  = 64;
    const REFUND_SCOPE_128 = 128;
    const REFUND_SCOPE_256 = 256;
    const REFUND_SCOPE_512 = 512;
    protected $table      = "trip_order_refund";
    protected $primaryKey = 'refund_id';
    protected $fillable
                          = [
            "user_id",          // 用户id
            "refund_sn",        // 退款单号内部,用于业务关联
            "out_refund_no",    // 外部退款单号 传给 支付的
            "order_sn",         // 订单单号
            "origin_order_sn",  // 改签原单单号
            "sub_order_sn",     // 子订单单号
            "need_pay_sn",      // 退款需支付,的支付单号
            "title",            // 支付标题
            "ticket_price",     // 票价,
            "refund_amount",    // 退款金额
            "refund_state",     // 退款状态
            "refund_type",      // 退款类型
            "trip_type",        // 出行类型
            "failed_reason",    // 退款失败的原因
            "out_refund_id",    // 支付的退款 id
            "out_order_id",     // 服务商关联的退票 order_id
            "refund_desc",
            "has_ticket",
            "refund_at",
            "success_at",
            "out_trade_no", // 即相关业务的支付单号
            "pay_amount",   // 即相关业务的支付单的支付总金额
            "refund_scope", // 作用域 暂未启用
        ];

    /**
     * 内部单号 默认和外部退款单号一致 ， 当中转订单第一程出票失败时 两笔订单统一 外部退款单号
     * 内部单号用于 区别内部退款明细归属
     *
     * @return string
     */
    public function getRefundSn(): string
    {
        return $this->getAttribute('refund_sn');
    }

    public function getRefundType(): int
    {
        return $this->getAttribute('refund_type');
    }

    public function hasTicket(): bool
    {
        return $this->getAttribute('has_ticket') == self::REFUND_HAS_TICKET;
    }

    public function getCreatedAt(): string
    {
        return $this->getAttribute('created_at');
    }

    public function isRefunding()
    {
        return $this->getRefundState() == self::REFUND_STATE_REFUNDING;
    }

    /**
     * 退款状态
     * @return int
     */
    public function getRefundState(): int
    {
        return $this->getAttribute('refund_state');
    }

    /**
     * 退款明细 合集
     * @return array
     */
    public function refund_amount_details(): array
    {
        $p = $this->passenger_details()->get()->toArray();
        $s = $this->unmatch_seat_details()->get()->toArray();
        $t = $this->no_ticket_details()->get()->toArray();

        return array_merge($p, $s, $t);
    }

    /**
     * 乘客申请退款的明细
     * @return HasMany
     */
    public function passenger_details()
    {
        return $this->hasMany(TripOrderPassengerRefundDetail::class, 'refund_sn', 'refund_sn');
    }

    /**
     * 选座未匹配的退款明细
     * @return HasMany
     */
    public function unmatch_seat_details()
    {
        return $this->hasMany(NoMatchSeatRefundDetail::class, 'refund_sn', 'refund_sn');
    }

    /**
     * 无票退款的明细
     * @return HasMany
     */
    public function no_ticket_details()
    {
        return $this->hasMany(NoTicketRefundDetail::class, 'refund_sn', 'refund_sn');
    }

    /**
     * 抢票任务的无票退款 分为抢票失败 和 手动取消
     *
     * @throws Exception
     * @throws Throwable
     */
    public function applyTaskNoTicketRefund(SnapTask $task, TrainOrder $order_info): ?self
    {
        $refunds      = [];
        $refund_datas = [];
        $excepts      = [];

        // 1 先查询是否 单独升级
        $aloneUpgrade = new dealAloneUpgradeTaskRefund($task, $order_info);
        $hasAlone     = $aloneUpgrade->hasAlonePayRecord(); // 单独支付的不含在 订单的退的details 里
        if (!empty($aloneUpgrade->getNeedRefundInfos())) {
            $refunds = array_merge($refunds, $aloneUpgrade->getNeedRefundInfos());
        }
        if (!empty($aloneUpgrade->getNewRefundData())) {
            $refund_datas = array_merge($refund_datas, $aloneUpgrade->getNewRefundData());
        }

        if ($hasAlone) {
            $excepts['type'][] = TripOrderCost::COST_TYPE_SNAP_HIGH_LEVEL_SERVICE_FEE;
        }

        // 2 查询 编辑需支付
        $editTask = new dealNoTicketEditTaskRefund($task, $order_info);
        $hasEdit  = $editTask->hasAlonePayRecord();
        if (!empty($editTask->getNeedRefundInfos())) {
            $refunds = array_merge($refunds, $editTask->getNeedRefundInfos());
        }
        if (!empty($editTask->getNewRefundData())) {
            $refund_datas = array_merge($refund_datas, $editTask->getNewRefundData());
        }

        if ($hasEdit) {
            $excepts['type'][] = TripOrderCost::COST_TYPE_SNAP_EDIT_TICKET_PRICE_DIFF;
        }
        $payRepo  = app(OrderPayRepositoryInterface::class);
        $costRepo = app(TripOrderCostsRepositoryInterface::class);

        $order_pay_sn   = $order_info->getPaySn();
        $order_pay_info = $payRepo->findOneByPaySn($order_pay_sn);
        $refund_sn      = "R{$order_pay_sn}";

        $refund_details = [];
        Log::info("excepts:".collect($excepts)->toJson());
        $order_costs = $costRepo->listByOrder($order_info, [], $excepts);
        $refund_data = [
            "user_id"         => $order_info->getUserId(),
            "refund_sn"       => $refund_sn,
            "out_refund_no"   => $refund_sn,
            "origin_order_sn" => $order_info->getOriginOrderSn(),
            "order_sn"        => $order_info->getOrderSn(),
            "ticket_price"    => 0,
            "refund_amount"   => $order_pay_info->getPayAmount(),
            "pay_amount"      => $order_pay_info->getPayAmount(),
            "has_ticket"      => self::REFUND_NO_TICKET,
            "refund_state"    => self::REFUND_STATE_REFUNDING, // 出票失败需要立即退款
            "title"           => "无票退款",
            "refund_type"     => self::REFUND_TYPE_NO_TICKET,
            "trip_type"       => $order_info->getTripType(),
            "out_trade_no"    => $order_pay_sn,
        ];

        foreach ($order_costs as $order_cost) {
            if ($hasAlone) {
                // 跳过单独支付的 高速抢票服务费
                if ($order_cost->getType() == TripOrderCost::COST_TYPE_SNAP_HIGH_LEVEL_SERVICE_FEE) {
                    continue;
                }
            }
            if ($hasEdit) {
                // 跳过 任务编辑票价差
                if ($order_cost->getType() == TripOrderCost::COST_TYPE_SNAP_EDIT_TICKET_PRICE_DIFF) {
                    continue;
                }
            }

            $refund_details[] = [
                "user_id"      => $order_info->getUserId(),
                "refund_sn"    => $refund_sn,
                "order_sn"     => $order_info->getOrderSn(),
                "out_trade_no" => $order_pay_sn,
                "type"         => $order_cost->getType(),
                "name"         => $order_cost->getName(),
                "amount"       => $order_cost->getPrice(),
                "count"        => $order_cost->getCount(),
            ];
        }
        $refund_data['details'] = $refund_details;
        $refund_datas[]         = $refund_data;
        Log::info($refund_datas);
        DB::transaction(function () use (&$refunds, $order_info, $refund_datas) {
            $refunds = array_merge($refunds, $this->applyNoTicketRefund($refund_datas));
        });
        Log::info("refunds:".collect($refunds)->toJson());
        foreach ($refunds as $refund_info) {
            try {
                $refund_info->refundNow('');
            } catch (Exception $e) {
                Log::error("任务无票退款失败:{$task->getTaskId()}".$e->getMessage());
            }
        }
        return null;
    }

    public function getOrderSn(): string
    {
        return $this->getAttribute('order_sn');
    }

    public function getTripType(): int
    {
        return $this->getAttribute('trip_type');
    }

    /**
     * 普通情况下的 , 无票退款
     *
     * @throws Exception
     */
    public function applyNoTicketRefund(array $refund_datas): array
    {
        $refunds = [];
        foreach ($refund_datas as $refund_data) {
            $detailModel = new NoTicketRefundDetail();
            $refund_info = null;
            foreach ($refund_data['details'] as $detail) {
                if ($detail = $detailModel->findOneByDetail($detail)) {
                    $refund_info = $detail->refund;
                    $up          = [];
                    if (!$refund_info->getOutTradeNo()) {
                        $up['out_trade_no'] = $refund_data['out_trade_no'];
                    }

                    if (!$refund_info->getTotalPayAmount()) {
                        $up['pay_amount'] = $refund_data['pay_amount'];
                    }
                    $up && $refund_info->update($up);
                    break;
                }
            }
            if (!$refund_info) {
                $refund_info = $this->create($refund_data);
                $detailModel->addMore($refund_info, $refund_data['details']);
            }
            if ($refund_info->isRefundedSuccess()) {
                continue;
            }
            $refunds[$refund_data['refund_sn']] = $refund_info;
        }
        return $refunds;
    }

    /**
     * 关联支付的交易单号 即 pay_info -> pay_sn
     * @return string
     */
    public function getOutTradeNo(): string
    {
        return $this->getAttribute('out_trade_no');
    }

    /**
     * 落地支付总金额
     * @return float
     */
    public function getTotalPayAmount(): float
    {
        return $this->getAttribute('pay_amount') ?: 0;
    }

    /**
     * 是否退款成功
     * @return bool
     */
    public function isRefundedSuccess(): bool
    {
        return $this->getRefundState() == self::REFUND_STATE_SUCCESS;
    }

    /**
     * 单笔订单立即退款
     *
     * @param string $reason
     *
     * @return mixed
     * @throws InvalidArgumentException
     * @throws InvalidConfigException
     */
    public function refundNow(string $reason = ''): mixed
    {
        if ($this->getRefundState() != 1) {
            return false;
        }
        $refund_amount = $this->getRefundAmount();
        $update_data   = [
            'refund_at' => Carbon::now()->toDateTimeString(),
        ];
        if ($refund_amount == 0) {
            $update_data['refund_desc'] = "订单可退款金额为0,完成";
            $update_data['success_at']  = Carbon::now()->toDateTimeString();
        }
        $this->update($update_data);


        if ($refund_amount == 0) {
            return false;
        }
        if ($this->getTripType() == TripType::TRIP_TYPE_TRAIN) {
            $orderRepo = app(TrainOrdersRepositoryInterface::class);
        } elseif ($this->getTripType() == TripType::TRIP_TYPE_PLANE) {
            $orderRepo = app(PlaneOrderRepositoryInterface::class);
        } else {

            throw new Exception("暂不支持的退款类型");

        }
        $order_info     = $orderRepo->getOrderInfo($this->getOrderSn());
        $order_pay_info = $order_info->order_pay_info();
        if (!$order_pay_info) {
            return $this->updateByOutRefundNoAndOrderState($this->getOutRefundNo(), [
                'refund_state'  => self::REFUND_STATE_FAILED,
                'failed_reason' => '订单无支付信息',
            ]);
        }
        $refund_amount = $this->getRefundAmount();
        $reason        = $reason ?: "退票退款";
        return $this->goRefund($order_info, $reason, $refund_amount);
    }

    /**
     * 退款金额
     * @return float
     */
    public function getRefundAmount(): float
    {
        return $this->getAttribute('refund_amount');
    }

    public function getOrderInfo(): ?OrdersInterface
    {
        if ($this->trip_type == TripType::TRIP_TYPE_TRAIN) {
            return $this->getTrainOrderInfo();
        }
        if ($this->trip_type == TripType::TRIP_TYPE_PLANE) {
            return $this->getPlaneOrderInfo();
        }
        return null;
    }

    public function getTrainOrderInfo(): TrainOrder
    {
        return $this->train_order()->first();
    }

    /**
     * 关联的订单
     * @return BelongsTo
     */
    public function train_order()
    {
        return $this->belongsTo(TrainOrder::class, 'order_sn', 'order_sn');
    }

    public function getPlaneOrderInfo(): PlaneOrder
    {
        return $this->plane_order()->first();
    }

    public function plane_order()
    {
        return $this->belongsTo(PlaneOrder::class, 'order_sn', 'order_sn');
    }

    /**
     * @param string $out_refund_no
     * @param array  $data
     *
     * @return bool
     */
    private function updateByOutRefundNoAndOrderState(string $out_refund_no, array $data): bool
    {
        if (!isset($data['refund_state'])) {
            Log::error("更新退款状态,未设置状态:{$out_refund_no}".json_encode($data, JSON_UNESCAPED_UNICODE));
            return false;
        }
        return $this->where('out_refund_no', $out_refund_no)->update($data);
    }

    /**
     * 外部单号 用于传入支付
     *
     * @return string
     */
    public function getOutRefundNo(): string
    {
        return $this->getAttribute('out_refund_no');
    }

    /**
     * @param TrainOrder|null $order_info
     * @param string          $reason
     * @param float|int       $refund_amount
     * @return mixed
     * @throws InvalidArgumentException
     * @throws InvalidConfigException
     */
    private function goRefund(?OrdersInterface $order_info, string $reason, float|int $refund_amount): mixed
    {
        $notify_url     = URL::to("/api/payment/wechat/callback");
        $order_pay_info = $order_info->order_pay_info();
        $wechatPay      = new WechatPay([
            'mchid' => $order_pay_info->getMchId(),
        ]);
        $app            = $wechatPay->getApplication();
        $api            = $app->getClient();
        $total_fee      = $this->getTotalPayAmount() * 100;
        $refund_fee     = $refund_amount * 100;
        $param          = [
            'out_trade_no'  => $this->getOutTradeNo(),
            'out_refund_no' => $this->getOutRefundNo(),
            'reason'        => $reason,
            'notify_url'    => $notify_url,
            'amount'        => [
                'refund'   => (int)$refund_fee,
                'total'    => (int)$total_fee,
                'currency' => 'CNY',
            ],
        ];
        Log::info(json_encode(['退款参数' => $param], JSON_UNESCAPED_UNICODE));
        $refundResp = $api->postJson("v3/refund/domestic/refunds", $param)->toArray();
        Log::info($refundResp);
        return $refundResp;
    }

    /**
     * 订单乘客申请退票退款,订单状态 默认为 待处理 需要退票成功后执行退款
     *
     * @throws Exception|Throwable
     */

    public function applyOrderPassengerTicketReturnRefund(array $refund_data): self
    {
        $refund_data['refund_state'] = self::REFUND_STATE_WAIT_REFUNDING;
        $refund_data['has_ticket']   = self::REFUND_HAS_TICKET;
        return $this->applyOrderPassengerRefund($refund_data);
    }

    /**
     * @throws Exception|Throwable
     */
    private function applyOrderPassengerRefund(array $refund_data): self
    {
        $validator = Validator::make($refund_data, [
            'user_id'       => "required",
            'has_ticket'    => "required",
            'refund_amount' => "required",
            'title'         => "required",
            'order_sn'      => "required",
            'refund_sn'     => "required",
            'out_refund_no' => "required",
            'out_trade_no'  => "required", // 支付单号
            'details'       => "required",
            'refund_type'   => "required",
            'trip_type'     => "required",
        ]);
        if ($validator->fails()) {
            throw new Exception($validator->errors()->first());
        }

        $refund_info = null;
        $details     = $refund_data['details'];
        if ($refund_data['trip_type'] == TripType::TRIP_TYPE_TRAIN) {
            $passengerRepo = app(TrainOrderPassengersRepositoryInterface::class);
        } elseif ($refund_data['trip_type'] == TripType::TRIP_TYPE_PLANE) {
            $passengerRepo = app(PlaneOrderPassengerRepositoryInterface::class);
        } else {
            throw new Exception("暂不支持的退款类型");
        }
//        dd($passengerRepo);
        $detailModel = new TripOrderPassengerRefundDetail();
        foreach ($details as $key => $detail) {
            $order_passengers = $passengerRepo->findOneOrderPassenger(
                $detail['order_sn'],
                $detail['passenger_id'],
                $detail['sequence']
            );
            if ($refund_detail = $detailModel->getPassengerAndRefundTypeDetails($order_passengers, $detail['type'], $detail['out_trade_no'])) {
                $refund_info = $refund_detail->refund;
                break;
            }
        }
        if (!$refund_info) {
            DB::transaction(function () use (&$refund_info, $refund_data) {
                $refund_info = $this->create($refund_data);
                $detail      = new TripOrderPassengerRefundDetail();
                return $detail->addMore($refund_info, $refund_data['details']);
            });
        }
        return $refund_info;
    }

    /**
     * 处理 票价差价退款 由于抢票任务可能升级座位 导致多次支付此处重构了
     * @throws Exception|Throwable
     */
    public function applyTicketPriceDiffsRefund(TrainOrder $order): void
    {
        $dealTicketPriceDiff = new dealTicketPriceDiffRefund($order);
        $exist_refund_infos  = $dealTicketPriceDiff->getNeedRefundInfos();
        $new_refund_datas    = $dealTicketPriceDiff->getNewRefundData();
        foreach ($new_refund_datas as $refund_data) {
            $exist_refund_infos[] = $this->applyOrderPassengerRefund($refund_data);
        }

        $passenger_name = $this->getRefundOrderPassengerName($order);
        $refund_reason  = $this->ticket_price_diff_refund_title($passenger_name);
        foreach ($exist_refund_infos as $refund_info) {
            $refund_info->refundNow($refund_reason);
        }
    }

    /**
     * 获取退款的乘客标题
     * @param OrdersInterface $order_info
     * @return string
     */
    private function getRefundOrderPassengerName(OrdersInterface $order_info): string
    {
        $name             = "";
        $passengers_count = $order_info->passengers()->count() ?: 0;
        if ($passengers_count > 0) {
            $name = $order_info->passengers()->first()?->getTrueName() ?: '';
        }
        return $passengers_count > 1 ? $name."..." : $name;
    }

    /**
     * 票价差价退款标题
     * @param string $passenger_name
     * @return string
     */
    private function ticket_price_diff_refund_title(string $passenger_name = ""): string
    {
        return Str::replace(['{NAME}', '{Name}'], [$passenger_name, $passenger_name], __("refund/order_or_ticket_refund_title.乘客{NAME}的车票差价"));
    }

    /**
     * @throws Exception|Throwable
     */
    public function applyUnMatchSelectSeatsRefund(TrainOrder $order): ?self
    {
        $trainOrderRepo = app(TrainOrdersRepositoryInterface::class);
        // TODO 退选座费
        $unMatchSelectSeats = $trainOrderRepo->getNotMatchSelectSeats($order);
        if (empty($unMatchSelectSeats)) {
            return null;
        }
        // 需要立即退款
        $unMatchSelectSeats['refund_state'] = self::REFUND_STATE_REFUNDING;
        $unMatchSelectSeats['refund_type']  = self::REFUND_TYPE_UNMATCHED_SEATS;
        $unMatchSelectSeats['trip_type']    = $order->getTripType();
        $validator                          = Validator::make($unMatchSelectSeats, [
            'user_id'       => "required",
            'has_ticket'    => "required",
            'refund_amount' => "required",
            'title'         => "required",
            'order_sn'      => "required",
            'refund_sn'     => "required",
            'out_refund_no' => "required",
            'details'       => "required",
        ]);

        if ($validator->fails()) {
            throw new Exception($validator->errors()->first());
        }

        $refund_info = null;
        $details     = $unMatchSelectSeats['details'];
        $detailModel = new NoMatchSeatRefundDetail();
        foreach ($details as $detail) {
            if ($detail = $detailModel->findOneByDetail($detail)) {
                $refund_info = $detail->refund;
                break;
            }
        }

        if (!$refund_info) {
            DB::transaction(function () use (&$refund_info, $unMatchSelectSeats) {
                $refund_info = $this->create($unMatchSelectSeats);
                $detail      = new NoMatchSeatRefundDetail();
                $detail->addMore($refund_info, $unMatchSelectSeats['details']);
            });
        }

        if (($refund_info instanceof self) && $refund_info->getRefundState() != 2) {
            $refund_info->getRefundState() != 1 && $refund_info->update([
                'refund_state' => self::REFUND_STATE_REFUNDING,
            ]);

            $order         = $refund_info->getTrainOrderInfo();
            $refund_reason = $this->unmatched_seat_refund_title($this->getRefundOrderPassengerName($order));
            $refund_info->refundNow($refund_reason);
        }

        return $refund_info;

    }

    /**
     * 未匹配坐席 退款标题
     * @param string $passenger_name
     * @param int    $type
     * @return string
     */
    private function unmatched_seat_refund_title(string $passenger_name, int $type = 1): string
    {
        $seat = "优选坐席";
        if ($type == 2) {
            $seat = "优选铺席";
        }
        return Str::replace(['{NAME}', '{Name}'], [$passenger_name, $passenger_name], __("refund/order_or_ticket_refund_title.乘客{NAME}未匹配到{$seat}退款"));
    }

    /**
     * 改签原单 需要退款 明细乘客
     *
     * @throws Exception
     * @throws Throwable
     */
    public function applyChangedOriginOrderNeedRefund(TrainOrder $order): ?self
    {
        $trainOrderRepo                     = app(TrainOrdersRepositoryInterface::class);
        $getChangeOriginOrderNeedRefundData = $trainOrderRepo->getChangeOriginOrderNeedRefund($order);
        if (empty($getChangeOriginOrderNeedRefundData)) {
            return null;
        }
        $getChangeOriginOrderNeedRefundData['refund_type'] = self::REFUND_TYPE_CHANGED_NEED_REFUND;
        $refund_info                                       = $this->applyOrderPassengerRefund($getChangeOriginOrderNeedRefundData);
        if ($refund_info->getRefundState() == 0) {
            $refund_info->update([
                'refund_state' => self::REFUND_STATE_REFUNDING,
            ]);
        }
        $refund_reason = $this->change_origin_order_need_refund_refund_title($this->getRefundOrderPassengerName($order));
        $refund_info->refundNow($refund_reason);
        return $refund_info;
    }

    private function change_origin_order_need_refund_refund_title(string $passenger_name = ""): string
    {
        return Str::replace(['{NAME}', '{Name}'], [$passenger_name, $passenger_name], __("refund/order_or_ticket_refund_title.乘客{NAME}的改签原车票退款"));
    }

    /**
     * 支付的退款  id
     *
     * @param string $refund_id
     *
     * @return bool
     */
    public function saveOutRefundId(string $refund_id): bool
    {
        return $this->update(['out_refund_id' => $refund_id]);
    }

    /**
     * 服务商的退款 order_id
     *
     * @param string $out_order_id
     *
     * @return bool
     */
    public function saveOutOrderId(string $out_order_id): bool
    {
        $businessMap = app(BusinessOrderSnMapRepositoryInterface::class);
        $businessMap->addOneMapByOrderTicketReturn($this, $out_order_id);
        return $this->update(['out_order_id' => $out_order_id]);
    }

    /**
     * @throws InvalidArgumentException
     * @throws InvalidConfigException
     * @throws Exception
     */
    public function refundsNow(array $refunds, string $refund_reason = '')
    {
        $total_refund_amount = 0;
        /**
         * @var self $refund_info
         */

        $out_refund_no = '';
        $refund_title  = '';
        $order_info    = null;
        foreach ($refunds as $refund_info) {
            if (!in_array($refund_info->getRefundState(), [1, 3])) {
                continue;
            }

            if ($refund_info->getTripType() == TripType::TRIP_TYPE_TRAIN) {
                $orderRepo = app(TrainOrdersRepositoryInterface::class);
            } elseif ($refund_info->getTripType() == TripType::TRIP_TYPE_PLANE) {
                $orderRepo = app(PlaneOrderRepositoryInterface::class);
            } else {
                throw new Exception("暂不支持的退款类型");
            }

            if ($out_refund_no && $refund_info->getOutRefundNo() != $out_refund_no) {
                throw new Exception("暂不支持,多业单号务退款");
            }

            if (!$order_info) {
                $order_info = $orderRepo->getOrderInfo($refund_info->getOrderSn());
            }
            if (!$refund_title) {
                $refund_title = $refund_info->getTitle();
            }
            if (!$out_refund_no) {
                $out_refund_no = $refund_info->getOutRefundNo();
            }
            $total_refund_amount += $refund_info->getRefundAmount();
        }

        $this->updateByOutRefundNo($out_refund_no, [
            'refund_at' => Carbon::now()->toDateTimeString(),
        ]);

        $order_pay_info = $order_info->order_pay_info();
        if (!$order_pay_info) {
            throw new Exception("支付数据不存在");
        }
        $refund_amount = $total_refund_amount;
        $reason        = $refund_reason ?: $refund_title;

        return $refund_info->goRefund($order_info, $reason, $refund_amount);
    }

    /**
     * 退款标题
     *
     * @return string
     */
    public function getTitle(): string
    {
        return $this->getAttribute('title');
    }

    public function updateByOutRefundNo(string $out_refund_no, array $data)
    {
        return $this->where('out_refund_no', $out_refund_no)->update($data);
    }

    /**
     * 获取退款
     *
     * @param string $refund_sn
     *
     * @return $this|null
     */
    public function findOneByRefundSn(string $refund_sn): ?self
    {
        return $this->where('refund_sn', $refund_sn)->first();
    }

    /**
     * 一个外部退款 单号可能对应多个 退款记录
     * @param string $out_refund_no
     * @return Collection
     */
    public function findByOutRefundNo(string $out_refund_no): Collection
    {
        return $this->where('out_refund_no', $out_refund_no)->get();
    }

    /**
     * 目前用于 单独支付的
     * @param string $out_refund_no
     * @return $this|null
     */
    public function findOneByOutRefundNo(string $out_refund_no): ?self
    {
        return $this->where('out_refund_no', $out_refund_no)->first();
    }

    /**
     * 退款成功
     *
     * @param RefundResult $refundResult
     *
     * @return void
     */
    public function refundSuccess(RefundResult $refundResult): void
    {
        $this->updateByOutRefundNoAndOrderState($refundResult->getOutRefundNo(), [
            'out_refund_id' => $refundResult->getOutRefundId(),
            'refund_state'  => self::REFUND_STATE_SUCCESS,
            'success_at'    => $refundResult->getRefundedAt(),
        ]);
    }

    /**
     * @throws Exception
     */
    public function refundFailedAndRetry(RefundResult $refundResult)
    {
        $cache_key = "ProcessWechatRefundCallback:failed:".$refundResult->getOutRefundNo();
        if (!Cache::has($cache_key)) {
            Cache::put($cache_key, 0, config('trip.ttl.day') * 3);
        }
        Cache::increment($cache_key, 1);
        $refund_state = self::REFUND_STATE_REFUNDING;
        $isThrow      = true;
        if (Cache::get($cache_key) > self::REFUND_FAILED_MAX_RETRY) {
            $refund_state = self::REFUND_STATE_FAILED;
            $isThrow      = false;
        }
        $this->updateByOutRefundNoAndOrderState($refundResult->getOutRefundNo(), [
            'failed_reason' => "退款失败,refund_status:{$refundResult->getRefundState()}",
            'refund_state'  => $refund_state,
        ]);
        if ($isThrow) {
            throw new Exception("退款失败,改为退款中,并重试{$refundResult->getOutRefundNo()}");
        }
    }
}
